package com.yya.ratelimiter.service;

import com.yya.ratelimiter.annotation.RateLimit;
import com.yya.ratelimiter.dao.WindowData;
import com.yya.ratelimiter.exception.RateLimitException;
import com.yya.ratelimiter.utils.Constant;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;

import java.util.Arrays;


/**
 * @Autor: yuanyongan
 * @Description: 滑动窗口限流算法实现
 * 使用循环进入的方法，当前单位时间内只用同一个数组进行存储
 * 每个请求按照公平锁的方式进行获取，保证每个请求到达时，当前单位时间内的所有请求都存储在数组内
 * 因此，每个请求达到时，只需要更新当前请求对应的节点即可
 * @Date 2023/1/28 13:20
 */

@Service
@Slf4j
public class SlidingWindow implements LimitationAlgorithm {


    private static final WindowData data = new WindowData();


    protected long calculateWindowStart(long timeMillis) {
        return timeMillis - timeMillis % data.getSlotTime();
    }

    private int getWindowIdx(long timeMills) {
        long timeId = timeMills / data.getSlotTime();
        return (int) (timeId % data.getSlotTime());
    }

    /**
     * 根据时间，计算对应的窗口划分的小段
     * 1、curNode为空说明当前时间还未覆盖到
     * 2、当前起始时间大于curNode时间点，说明需要对当前时间段进行覆盖
     * 3、等于说明前后两次请求在同一个时间段内
     *
     * @param timeMills 时间戳
     * @return 返回替换片段的请求数之差
     */
    private int updateWindow(long timeMills) {
        if (timeMills < 0) {
            return 0;
        }
        int idx = getWindowIdx(timeMills);
        // 当前时间对应的窗口开始时间
        long windowStart = calculateWindowStart(timeMills);
        Fragment curNode = data.getSlots()[idx];
        if (curNode == null || windowStart > curNode.getBeginTime()) {
            Fragment node = new Fragment(windowStart);
            data.getSlots()[idx] = node;
        }
        data.getSlots()[idx].add(1L);
        return curNode == null ? 1 : (int) (data.getSlots()[idx].getCounter() - curNode.getCounter());
    }


    @Override
    public void acquire(RateLimit rateLimit, MethodSignature signature) {
        // 保证多个请求并发时，更新的正确性
        synchronized (data) {
            if (!data.isInit()) {
                initData(rateLimit);
                data.setInit(true);
            }
            initData(rateLimit);
            long curTime = System.currentTimeMillis();

            // 获取差值
            int num = updateWindow(curTime);

            // 统计范围时间内的请求数目
            // TODO:后面可以保留总数，只更新差值数
            long allFlow = Arrays.stream(data.getSlots()).mapToLong(Fragment::getCounter).sum();

            if (allFlow > rateLimit.count()) {
                log.info("当前接口达到最大限流次数");
                throw new RateLimitException(500, "当前接口达到最大限流次数");
            }
            log.info("一段时间内允许请求的次数:{}, 当前请求次数:{}", rateLimit.count(), allFlow);
        }
    }

    private void initData(RateLimit rateLimit) {
        data.setLimitNum(rateLimit.count());
        data.setWindowDuration(rateLimit.time());
        data.setSlotTime(rateLimit.time() / Constant.slotNum);
    }

    @Override
    public void logInfo() {

    }
}
